/**
 * @file test_temp.cpp
 * @author Young_jam
 * @brief VBO使用
 * @version 1.0
 * @date 2023/01/12
 * @ref https://blog.csdn.net/qq_32974399/article/details/103956589
 * @ref https://cloud.tencent.com/developer/article/1893989
 */

/**
 * @name VBO
 * @brief
 * 用于在显存中提前开辟一块内存,用于缓存顶点数据,从而避免每次绘制都要在cpu和gpu之间拷贝数据,通过GL_ARRAY_BUFFER指定
 *
 * @name EBO
 * @brief
 * 与VBO功能相同,但是用于缓存图元索引(顶点序号)数据,通过GL_ELEMENT_ARRAY_BUFFER指定
 *
 * @name VAO
 * @brief
 * 顶点数组对象,用于管理VBO和EBO,减少glBindBuffer,glEnableVertexAttribArray,glVertexAttribPointer这些调用操作,高效地实现在顶点数组配置之间切换
 */
#include "c_defines.h"
#include "glut_win.h"
#include "shader_mgr.h"
#include <glm/ext.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>

using namespace std;

// #define USE_VAO
#ifdef USE_VAO
GLuint VAO = 0;
GLuint VBO = 0;
#endif
GLuint VBOs[2] = {0, 0};

void Init(void)
{
    glClearColor(.22f, .21f, .23f, 1.f);

    glEnable(GL_DEPTH_TEST);

    auto shader = CShaderManager::GetInstance().CreateNewShader("model");
    if (!shader->LoadShader("../shader/model.vert", "../shader/model.frag", 2, 0, "vVertexs", 1, "inColor"))
    {
        ERROR("load shader failed!");
        exit(EXIT_FAILURE);
    }

    GLfloat  vertices[] = {10.f, 0.f, -10.f, 1.0f, 0.f, 0.f,  -10.f, 0.f, -5.f,
                          0.f,  1.f, 0.f,   5.f,  0.f, 10.f, 0.f,   0.f, 1.f};
    GLushort indices[3] = {0, 1, 2};

#ifdef USE_VAO
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
#else
    // 批量创建VBO ID
    glGenBuffers(2, VBOs);

    /**
     * @brief 绑定VBO,并配置类型
     * @param [in] target
     *  - GL_ARRAY_BUFFER 顶点数组
     *  - GL_ELEMENT_ARRAY_BUFFER 索引数组
     */
    glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
    /**
     * @brief 复制数据到opengl缓冲中
     * @param [in] target 缓冲区类型
     * @param [in] size 缓冲区大小
     * @param [in] data 数据首地址
     * @param [in] usage
     *  - GL_STATIC_DRAW 数据不会或几乎不会改变
     *  - GL_DYNAMIC_DRAW 数据会被改变很多
     *  - GL_STREAM_DRAW 数据每次绘制都会改变
     */
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBOs[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
#endif

    /**
     * @brief 解释位置属性
     * @param [in] index 该属性对应着色器中哪个location
     * @param [in] size 属性规模,比如一组顶点属性由3个值组成
     * @param [in] type 属性值的数据类型
     * @param [in] normallized 数据是否归一化
     * @param [in] stride 每组属性之间的间隔
     * @param [in] pointer 该属性在缓冲数组中的偏移
     */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void *)0);
    glEnableVertexAttribArray(0);
    // 解释颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void *)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

#ifdef USE_VAO
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
#else
    // 解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif
}

void Display()
{
    static auto shader = CShaderManager::GetInstance().GetShader("model");

    static glm::mat4 projection = glm::perspective(glm::radians(75.f), GLfloat(640) / GLfloat(480), 1.f, 100000.f);
    static glm::mat4 view = glm::lookAt(glm::vec3(0.f, 15.f, -20.f),    // 眼睛在哪里
                                        glm::vec3(0.f),                 // 眼睛看向哪里
                                        glm::vec3(0.f, 1000.f, -10.f)); // 头部指向哪里
    static glm::mat4 model = glm::scale(glm::mat4(1.f), glm::vec3(1.f));

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shader->Enable();

    shader->SetUniformMatrix4fv("projection", glm::value_ptr(projection));
    shader->SetUniformMatrix4fv("view", glm::value_ptr(view));
    shader->SetUniformMatrix4fv("model", glm::value_ptr(model));
#ifdef USE_VAO
    glBindVertexArray(VAO);

    /**
     * @brief 使用启用数组中的顺序元素来构造基本几何图片
     * @param mode 基础图元类型
     * @param first 已启用数组中元素的起始索引
     * @param count 要显示的元素个数
     */
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glBindVertexArray(0);
#else

    glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBOs[1]);

    /**
     * @brief
     * 使用count个元素定义的一个几何序列绘制图形,这些元素索引值保存的indices中
     * @param [in] mode 图元类型
     * @param [in] count 元素个数,一般是点的个数
     * @param [in] type indices数组的数据类型
     * @param [in] indices indices数组地址或偏移
     */
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (const void *)0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif

    shader->Disable();

    glFinish();
}

int main(int argc, char **argv)
{
    CGlutWindow window("shader test");

    window.Init(&argc, argv);

    Init();

    window.RegisterCallbackFunc([]() {
        glutDisplayFunc(Display);
        glutIdleFunc(Display);
    });

    window.MainLoop();

    return 0;
}